#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PHMEE.Diagnosis;
using PHMEE.Entities.Diagnosis;
using PHMEE.IRepositories.Diagnosis;
using PHMEE.PHM.Algorithms.Diagnosis;
using Volo.Abp;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;

namespace PHMEE.Managers.Diagnosis;

public class FaultDiagnosisDomainService : DomainService, IFaultDiagnosisDomainService
{
    private readonly IPointRepository _pointRepository;
    private readonly IVibChannelParaRepository _vibChannelParaRepository;
    private readonly IVesselManager _vesselManager;

    private readonly IMainLubricatingOilPumpFaultDiagnosisDomainService
        _mainLubricatingOilPumpFaultDiagnosisDomainService;

    private readonly IAirCompressorFaultDiagnosisDomainService _airCompressorFaultDiagnosisDomainService;
    private readonly IOilFuelSeparatorFaultDiagnosisDomainService _oilFuelSeparatorFaultDiagnosisDomainService;
    private readonly IAirBlower1FaultDiagnosisDomainService _airBlower1FaultDiagnosisDomainService;
    private readonly IAirBlower2FaultDiagnosisDomainService _airBlower2FaultDiagnosisDomainService;
    private readonly ITurbineFaultDiagnosisDomainService _turbineFaultDiagnosisDomainService;
    private readonly IDieselGeneratorFaultDiagnosisDomainService _dieselGeneratorFaultDiagnosisDomainService;
    private readonly IWaveDataAlarmTDRepository _waveDataAlarmTDRepository;
    private readonly IAlarmRecordManager _alarmRecordManager;
    private readonly IRepository<AlarmRecord, Guid> _alarmRecordRepository;
    private readonly IReadOnlyRepository<Equipment, Guid> _equipmentRepository;

    private readonly ILogger<FaultDiagnosisDomainService> _logger;
    // private readonly IAlarmRecordCreatedEventPublisher _alarmRecordCreatedEventPublisher;

    public FaultDiagnosisDomainService(
        IPointRepository pointRepository,
        IVibChannelParaRepository vibChannelParaRepository,
        IVesselManager vesselManager,
        IMainLubricatingOilPumpFaultDiagnosisDomainService mainLubricatingOilPumpFaultDiagnosisDomainService,
        IAirCompressorFaultDiagnosisDomainService airCompressorFaultDiagnosisDomainService,
        IOilFuelSeparatorFaultDiagnosisDomainService oilFuelSeparatorFaultDiagnosisDomainService,
        IAirBlower1FaultDiagnosisDomainService airBlower1FaultDiagnosisDomainService,
        IAirBlower2FaultDiagnosisDomainService airBlower2FaultDiagnosisDomainService,
        ITurbineFaultDiagnosisDomainService turbineFaultDiagnosisDomainService,
        IDieselGeneratorFaultDiagnosisDomainService dieselGeneratorFaultDiagnosisDomainService,
        IWaveDataAlarmTDRepository waveDataAlarmTDRepository,
        IAlarmRecordManager alarmRecordManager,
        IRepository<AlarmRecord, Guid> alarmRecordRepository,
        IReadOnlyRepository<Equipment, Guid> equipmentRepository,
        // IAlarmRecordCreatedEventPublisher alarmRecordCreatedEventPublisher
        ILogger<FaultDiagnosisDomainService> logger
        )
    {
        _pointRepository = pointRepository;
        _vibChannelParaRepository = vibChannelParaRepository;
        _vesselManager = vesselManager;
        _mainLubricatingOilPumpFaultDiagnosisDomainService = mainLubricatingOilPumpFaultDiagnosisDomainService;
        _airCompressorFaultDiagnosisDomainService = airCompressorFaultDiagnosisDomainService;
        _oilFuelSeparatorFaultDiagnosisDomainService = oilFuelSeparatorFaultDiagnosisDomainService;
        _airBlower1FaultDiagnosisDomainService = airBlower1FaultDiagnosisDomainService;
        _airBlower2FaultDiagnosisDomainService = airBlower2FaultDiagnosisDomainService;
        _turbineFaultDiagnosisDomainService = turbineFaultDiagnosisDomainService;
        _dieselGeneratorFaultDiagnosisDomainService = dieselGeneratorFaultDiagnosisDomainService;
        _waveDataAlarmTDRepository = waveDataAlarmTDRepository;
        _alarmRecordManager = alarmRecordManager;
        _alarmRecordRepository = alarmRecordRepository;
        _equipmentRepository = equipmentRepository;
        _logger = logger;
        // _alarmRecordCreatedEventPublisher = alarmRecordCreatedEventPublisher;
    }

    /// <summary>
    /// 故障诊断调度器
    /// </summary>
    /// <param name="pointId"></param>
    /// <param name="timeStamp"></param>
    /// <returns></returns>
    public async Task FaultDiagnosisDispatcherAsync(Guid pointId, DateTime timeStamp)
    {
        // TODO: 确定 timeStamp 为 UTC 时间

        // 取得测点
        var point = await _pointRepository.PhmeeGetById(pointId);

        // 只有振动类测点且为加速度测点才需要进行故障诊断
        if (point.Category is not PointCategoryEnum.Vibrate
            || point.Type is not PointTypeEnum.Acceleration
            || point is not AcceleratePoint acceleratePoint)
        {
            return;
        }

        // 赋值测点的所有属性
        acceleratePoint.DeserializeProperties();

        Unit? unit = null;
        Equipment? equipment = null;

        // 根据测点 OwnerType 查询
        switch (acceleratePoint.OwnerType)
        {
            // 机组
            case nameof(Unit):
                (_, unit) = await _vesselManager.GetUnitRelation(acceleratePoint.OwnerId);
                break;
            // 设备
            case nameof(Equipment):
                (_, unit, equipment) = await _vesselManager.GetEquipmentRelation(acceleratePoint.OwnerId);
                break;
            // 部件
            case nameof(Component):
                (_, unit, _, _) = await _vesselManager.GetComponentRelation(acceleratePoint.OwnerId);
                break;
            // 如果测点不属于上述三个之一，则目前无需进行故障诊断
            default:
                return;
        }
        
        if (acceleratePoint.OwnerType is nameof(Unit))
        {
            // 关联到机组的测点，如果 unit 为 null，则抛出异常
            if (unit is null)
            {
                throw new BusinessException($"测点 Point: {acceleratePoint.Id} 关联到了机组，但机组为 null");
            }
        }
        else if (acceleratePoint.OwnerType is nameof(Equipment))
        {
            // 部分测点关联到设备，如果设备为 null，则抛出异常
            if (equipment is null)
            {
                throw new BusinessException($"测点 Point: {acceleratePoint.Id} 关联到了设备，但设备为 null");
            }
        }

        // 报警数据
        var data = await _waveDataAlarmTDRepository.GetAsync(acceleratePoint.Id, timeStamp);

        if (data.Length == 0)
        {
            throw new BusinessException($"测点 Point: {acceleratePoint.Id} raw data 为空");
        }

        PointPosition pointPosition = (PointPosition)acceleratePoint.Position;
        SysPara sysPara = await _vibChannelParaRepository.GetByVibChannelPara(acceleratePoint.Id);
        if (sysPara is null)
        {
            throw new BusinessException($"测点 Point: {acceleratePoint.Id} sysPara 为 null");
        }

        float samplingFrequency = sysPara.SamplingFrequency;

        if (samplingFrequency == 0)
        {
            throw new BusinessException($"测点 Point: {acceleratePoint.Id} samplingFrequency 为 0 (default 值)");
        }

        string[] faultCodes = [];
        
        // 由于 GetUnitRelation 等方法返回的 Unit 不携带 Unit 的 Equipments 信息，所以需要重新获取 Unit 其下的设备
        UnitWithEquipsDto unitWithEquips = await GetUnitWithEquipmentsAsync(unit);

        switch (acceleratePoint.DiagnosisAlg)
        {
            case DiagnosisAlgEnum.PumpAlg:
                faultCodes = await _mainLubricatingOilPumpFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, data);
                break;
            case DiagnosisAlgEnum.AirCompressorAlg:
                faultCodes = await _airCompressorFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, data);
                break;
            case DiagnosisAlgEnum.OilFuelSeparatorAlg:
                faultCodes = await _oilFuelSeparatorFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, data);
                break;
            case DiagnosisAlgEnum.AirBlowerAlg1:
                faultCodes = await _airBlower1FaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, data);
                break;
            case DiagnosisAlgEnum.AirBlowerAlg2:
                faultCodes = await _airBlower2FaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, data);
                break;
            case DiagnosisAlgEnum.TurbineAlg:
                if (equipment is null)
                {
                    throw new BusinessException($"测点 Point: {acceleratePoint.Id} 使用 TurbineAlg，但设备为 null");
                }
                faultCodes = await _turbineFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, equipment, data);
                break;
            case DiagnosisAlgEnum.DieselGeneratorAlg:
                faultCodes = await _dieselGeneratorFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
                    samplingFrequency, unitWithEquips, data);
                break;
            case DiagnosisAlgEnum.None:
            default:
                _logger.LogInformation("测点 Point: {acceleratePoint.Id} 无需进行故障诊断", acceleratePoint.Id);
                return;
        }
            
        // if (acceleratePoint.OwnerType is nameof(Unit))
        // {
        //     // 根据 Unit.UnitEnum 判断机组类型，调用不同的故障诊断方法
        //     switch (unit.Type)
        //     {
        //         // 主滑油泵算法
        //         case UnitEnum.LubricatingOilPumpUnit: // 滑油泵
        //         case UnitEnum.BallastPumpUnit: // 压载泵
        //         case UnitEnum.SeaWaterPumpUnit: // 海水泵
        //             faultCodes =
        //                 await _mainLubricatingOilPumpFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
        //                     samplingFrequency, unitWithEquips, data);
        //             break;
        //         // 空气压缩机算法
        //         case UnitEnum.StartingAirCompressUnit:
        //             faultCodes =
        //                 await _airCompressorFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
        //                     samplingFrequency, unitWithEquips, data);
        //             break;
        //         // 滑油-燃油分油机算法
        //         case UnitEnum.LubricatingOilSepaUnit: // 滑油分油机
        //         case UnitEnum.FuelOilSeparatorUnit: // 燃油分油机
        //             faultCodes =
        //                 await _oilFuelSeparatorFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
        //                     samplingFrequency, unitWithEquips, data);
        //             break;
        //         // 风机算法
        //         case UnitEnum.CombustionAirBlowerUnit: // 燃烧鼓风机
        //             faultCodes =
        //                 await _airBlowerFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
        //                     samplingFrequency, unitWithEquips, data);
        //             break;
        //         // 柴油发电机组算法
        //         // TODO: 需确认柴油发电机组是否是 UnitEnum 中的主发电机机组
        //         case UnitEnum.MainGeneratorUnit: // 主发电机机组
        //             faultCodes =
        //                 await _dieselGeneratorFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
        //                     samplingFrequency, unitWithEquips, data);
        //             break;
        //         // 如果 Unit.Type 不在上述范围内，则目前无需进行故障诊断，因为没有对应的算法
        //         default:
        //             return;
        //     }
        // }
        // else if (acceleratePoint.OwnerType is nameof(Equipment))
        // {
        //     // 部分测点关联到设备，如果设备为 null，则抛出异常
        //     if (equipment is null)
        //     {
        //         throw new BusinessException($"测点 point: {acceleratePoint.Id} 设备为 null");
        //     }
        //
        //     // 根据 Equipment.Type 判断设备类型，调用不同的故障诊断方法
        //     switch (equipment.Type)
        //     {
        //         // 透平算法
        //         case EquipmentEnum.Turbocharger:
        //             faultCodes = await _turbineFaultDiagnosisDomainService.FaultDiagnosisAsync(pointPosition,
        //                 samplingFrequency, unitWithEquips, equipment, data);
        //             break;
        //         default:
        //             return;
        //     }
        // }
        
        // 去重
        faultCodes = faultCodes.Distinct().ToArray();
        
        // 如果 faultCodes 包含 "no_fault"
        bool isNoFault = faultCodes.Contains("no_fault");
        
        _logger.LogInformation("故障诊断完成：Point: {acceleratePoint.Id}, ts: {timeStamp}, faultCodes: {faultCodes)}", acceleratePoint.Id, timeStamp.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), string.Join(",", faultCodes));

        // 上位机计算和重新划分 AlarmLevel，以及报警的特征值
        var featureAlarmLevels = await GetFeatureAlarmLevelsAsync(acceleratePoint.Id, timeStamp);

        var alarmRecord = _alarmRecordManager.Create(
            unit.VesselId,
            unit.Id,
            equipment?.Id ?? null,
            acceleratePoint.Id,
            timeStamp,
            faultCodes,
            isNoFault,
            featureAlarmLevels
        );

        await _alarmRecordRepository.InsertAsync(alarmRecord);

        _logger.LogInformation("AlarmRecord 创建完成：Point: {acceleratePoint.Id}, ts: {timeStamp}", acceleratePoint.Id, timeStamp.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));

        // // 发布 AlarmRecord 创建事件，目前只有这一个方法会创建 AlarmRecord，无需将发布事件的操作移动到 Entity 中，方便单元测试
        // await _alarmRecordCreatedEventPublisher.PublishAlarmRecordCreatedEventAsync(alarmRecord, point);
    }

    /// <summary>
    /// 计算各个特征值及对应的报警等级
    /// </summary>
    /// <param name="pointId"></param>
    /// <param name="timeStamp"></param>
    /// <returns></returns>
    private async Task<Dictionary<AlarmFeatureValue, AlarmLevel>> GetFeatureAlarmLevelsAsync(Guid pointId,
        DateTime timeStamp)
    {
        var featureValues = await _waveDataAlarmTDRepository.GetFeatureValuesAsync(pointId, timeStamp);

        if (featureValues is null)
        {
            throw new BusinessException(
                $"Point: {pointId}, ts: {timeStamp:yyyy-MM-ddTHH:mm:ss.fffZ} feature values is null");
        }

        Dictionary<AlarmFeatureValue, AlarmLevel> featureAlarmLevels = [];

        // 加速度有效值
        if (featureValues.AccRms >= featureValues.AccRmsAlarm)
        {
            featureAlarmLevels.Add(AlarmFeatureValue.AccRms, AlarmLevel.D);
        }
        else if (featureValues.AccRms >= featureValues.AccRmsWarning)
        {
            featureAlarmLevels.Add(AlarmFeatureValue.AccRms, AlarmLevel.C);
        }

        // 加速度平均峰值
        if (featureValues.AccAvgPeak >= featureValues.AccAvgPeakAlarm)
        {
            featureAlarmLevels.Add(AlarmFeatureValue.AccAvgPeak, AlarmLevel.D);
        }
        else if (featureValues.AccAvgPeak >= featureValues.AccAvgPeakWarning)
        {
            featureAlarmLevels.Add(AlarmFeatureValue.AccAvgPeak, AlarmLevel.C);
        }

        // 速度有效值
        if (featureValues.VelRms >= featureValues.VelRmsAlarm)
        {
            featureAlarmLevels.Add(AlarmFeatureValue.VelRms, AlarmLevel.D);
        }
        else if (featureValues.VelRms >= featureValues.VelRmsWarning)
        {
            featureAlarmLevels.Add(AlarmFeatureValue.VelRms, AlarmLevel.C);
        }

        return featureAlarmLevels;
    }

    /// <summary>
    /// 根据 Unit 获取 Unit 以及其下的设备
    /// </summary>
    /// <param name="unit"></param>
    /// <returns></returns>
    public async Task<UnitWithEquipsDto> GetUnitWithEquipmentsAsync(Unit unit)
    {
        var unitEquips = await _equipmentRepository.GetListAsync(x => x.UnitId == unit.Id);

        return new UnitWithEquipsDto
        {
            Unit = unit,
            UnitEquips = unitEquips
        };
    }
}

public class UnitWithEquipsDto
{
    public Unit Unit { get; set; } = null!;

    public List<Equipment> UnitEquips { get; set; } = [];
}